package cn.skyquant.quant4j.jforex.strategy.apollo;

import cn.skyquant.quant4j.api.dto.ResultDTO;
import cn.skyquant.quant4j.api.enums.BorkerType;
import cn.skyquant.quant4j.api.forex.ForexConfigSeriesDTO;
import cn.skyquant.quant4j.api.forex.ForexServiceProxy;
import cn.skyquant.quant4j.api.ylzc.YlzcDTO;
import cn.skyquant.quant4j.api.ylzc.YlzcMap;
import cn.skyquant.quant4j.api.ylzc.YlzcServiceProxy;
import cn.skyquant.quant4j.jforex.sdk.enums.LabelType;
import cn.skyquant.quant4j.jforex.sdk.enums.OpenType;
import cn.skyquant.quant4j.jforex.sdk.strategy.*;
import cn.skyquant.quant4j.jforex.sdk.strategy.BaseConfig;
import cn.skyquant.quant4j.jforex.sdk.util.QuantUtil;
import cn.skyquant.quant4j.sdk.enums.CloseType;
import cn.skyquant.quant4j.sdk.enums.TradeDirection;
import cn.skyquant.quant4j.sdk.util.constant.EAConstants;
import cn.skyquant.quant4j.sdk.util.time.CalendarUtils;
import com.dukascopy.api.*;
import com.dukascopy.api.feed.IBarFeedListener;
import com.dukascopy.api.feed.IFeedDescriptor;
import com.dukascopy.api.indicators.IIndicator;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 阿波罗
 * 固定点位逆市加仓策略
 *
 * @author harley-dog
 */
@Library("quant4j-api.jar;quant4j-sdk.jar;quant4j-jforex-sdk.jar;freemarker-2.3.28.jar;httpclient-4.5.6.jar;httpcore-4.4.10.jar;fastjson-1.2.49.jar;commons-codec-1.11.jar")
public class Apollo023 extends BaseStrategy implements IBarFeedListener {
    @Configurable("逆市是否开首仓")
    public boolean R_Trade = true;
    @Configurable("加仓数列列表")
    public String seriesIds = "102,103,104,105,106,107,108,109,110";
    @Configurable("加仓数列对应波动率最大浮动范围")
    public int maxATRSeries = 100;
    @Configurable("移动止损点")
    public double trailingStep = 10;
    @Configurable("开启移动止损")
    public boolean moveSL = true;
    @Configurable("1:货币,方向,倍率,波动率,布林差")
    public String str0 = "EURUSD,A,1,5,0.2";
    @Configurable("2:货币,方向,倍率,波动率,布林差")
    public String str1 = "GBPUSD,A,1,5,0.2";
    @Configurable("3:货币,方向,倍率,波动率,布林差")
    public String str2 = "USDCAD,A,1,5,0.2";
    @Configurable("4:货币,方向,倍率,波动率,布林差")
    public String str3 = "";
    @Configurable("5:货币,方向,倍率,波动率,布林差")
    public String str4 = "";

    @Configurable("大K线:波动率和布林带K柱测试个数")
    public int indiNums = 30;
    @Configurable("大K线:计算天数")
    public int ylzcNums = 280;
    @Configurable("遇到X级压力支撑位进行止损")
    public short stop_loss_level = 5;
    @Configurable("首仓距离止损位之间至少可以加几仓")
    public short first_loss_limit_position = 1;
    @Configurable("1-5级压力支撑位暂停时间倍率")
    public String pause_time_m = "1,4,15,55,200";
    @Configurable("止损值为几倍日波动率")
    public double stop_loss_atr = 0.9;
    @Configurable("上一次止损后必须产生新支撑压力位")
    public boolean last_sl_new_ylzc = true;
    @Configurable("开单允许最大滑点")
    public double slippage = 5;
    @Configurable("订单成交日志")
    public boolean log_order_fill = true;
    @Configurable("订单平仓日志")
    public boolean log_order_close = true;
    @Configurable("订单提交日志")
    public boolean log_order_submit = true;
    @Configurable("订单合并日志")
    public boolean log_order_merge = false;
    @Configurable("订单取消日志")
    public boolean log_order_cancel = false;
    @Configurable("压力支撑位日志")
    public boolean log_ylzc = false;
    @Configurable("ATR,布林带日志")
    public boolean log_daily_open = false;
    @Configurable("不开单原因日志")
    public boolean log_order_reason = false;
    @Configurable("移动止损止盈日志")
    public boolean log_order_move = false;
    @Configurable("显示图表")
    public boolean show_chart = false;
    @Configurable("反向加倍")
    public String reflect_str = "6:2,10:3,20:2";

    YlzcServiceProxy ylzcServiceProxy;
    ForexServiceProxy forexServiceProxy;
    int[] pause_times;
    ATRSeriesManager asm;
    ITimeDomain timeDomain;//下一个停盘时间
    ReflectManager reflectManager;

    final Map<Instrument, InstrumentEntity> insMap = new HashMap<>();//交易品种实体
    final Map<Instrument, Double> atrMap = new HashMap<>();//波动率
    final Map<Instrument, Boll> bollMap = new HashMap<>();//布林值
    final Map<Instrument, YlzcMap> ylzcMap = new HashMap<>();//压力支撑位
    final Map<String, IOrder> lastCloseOrder = new HashMap<>();//记录上一个平仓的订单

    /*计算波动率和ATR的常量，以后说不定会替换成变量*/
    private static final double nbDevUp = 2;
    private static final double nbDevDn = 2;
    private static final IIndicators.MaType maType = IIndicators.MaType.EMA;
    private static final Map<Instrument, OfferSide> offerSideMap = new HashMap<>();
    private static final IIndicators.AppliedPrice appliedPrice = IIndicators.AppliedPrice.CLOSE;

    @Override
    protected String getVersion() {
        return "021";
    }

    @Override
    protected String getName() {
        return "Apollo" + eaId;
    }

    @Override
    protected String getEaName() {
        return "Apollo";
    }

    @Override
    public final void onStopEx() {
        super.onStopEx();
        forexServiceProxy = null;
        asm = null;
        timeDomain = null;
        reflectManager = null;
        insMap.clear();
        atrMap.clear();
        ylzcMap.clear();
        lastCloseOrder.clear();
        offerSideMap.clear();
    }

    @Override
    public void onTickEx(Instrument instrument, ITick tick) {
    }

    @Override
    public void onBarEx(Instrument instrument, Period period, IBar askBar, IBar bidBar) {
    }

    //初始化方法
    @Override
    public void init(InitEntity initEntity) {
        ylzcServiceProxy = new YlzcServiceProxy(account.getAccountId(), BorkerType.DUKASCOPY);
        forexServiceProxy = new ForexServiceProxy(account.getAccountId(),BorkerType.DUKASCOPY);
        long begin = System.currentTimeMillis();
        reflectManager = new ReflectManager(reflect_str);
        String[] strArr = {str0, str1, str2, str3, str4};
        for (int i = 0; i < strArr.length; i++) {
            if (strArr[i] != null && strArr[i].trim().length() > 0) {
                String[] s = strArr[i].split(",");
                if (s.length == 5
                        && s[0].trim().length() > 0
                        && s[1].trim().length() > 0
                        && s[2].trim().length() > 0
                        && s[3].trim().length() > 0
                        && s[4].trim().length() > 0) {
                    Instrument instrument = Instrument.valueOf(s[0]);
                    if (instrument == null) {
                        if (log_order_reason) warn("can't trade");
                        continue;
                    }
                    if (!insMap.containsKey(instrument)) {
                        insMap.put(instrument, new InstrumentEntity(instrument));
                    }
                    try {
                        String tradeDirection = s[1];
                        Double multiple = Double.parseDouble(s[2]);
                        Double atr = Double.parseDouble(s[3]);
                        Double boll = Double.parseDouble(s[4]);
                        //逆市
                        ApolloConfig a = new ApolloConfig(getName(), i, multiple, 0, instrument, atr, boll, tradeDirection);//逆趋势
                        initEntity.a.add(a);
                        //货币组
                        initEntity.b.add(instrument);
                        if (log_start_stop) {
                            out(a.toString());
                        }
                        offerSideMap.put(instrument, a.tradeDirection.isLong() ? OfferSide.ASK : OfferSide.BID);
                    } catch (NumberFormatException e) {
                        initEntity.addReason("init " + i + "config error:" + e);
                    }
                } else {
                    if (log_start_stop)
                        warn("config[%d] must be 6 params[symbol,direction,rate,pointdiff,atr,boll],but it find %d params", i, s.length);
                }
            }
        }
        if (initEntity.a.isEmpty()) {
            initEntity.addReason("apollo config is empty");
        }
        if (seriesIds == null || seriesIds.length() == 0) {
            initEntity.addReason("seriesIds is null");
            return;
        }
        String[] seriesIdArr = seriesIds.split(",");
        if (seriesIdArr.length == 0) {
            initEntity.addReason("seriesIds is invalid");
            return;
        }
        String[] pause_time_arr = pause_time_m.split(",");
        if (pause_time_arr.length == 0) {
            initEntity.addReason("pause_time_m is invalid");
            return;
        }
        pause_times = new int[pause_time_arr.length];
        for (int i = 0; i < pause_time_arr.length; i++) {
            pause_times[i] = Integer.parseInt(pause_time_arr[i]);
        }
        int[] seriesIdIntArr = new int[seriesIdArr.length];
        for (int i = 0; i < seriesIdArr.length; i++) {
            seriesIdIntArr[i] = Integer.parseInt(seriesIdArr[i]);
        }
        asm = new ATRSeriesManager(seriesIdIntArr);
        for (Series series : asm.seriesList)
            if (series.valid && log_start_stop) {
                out("%d series ,totalPip=%d", series.seriesId, series.totalPip);
            }
        if (!asm.valid) {
            initEntity.addReason("asm invalid");
        }
        IBarFeedListener listener = this;
        initEntity.b.forEach(x -> {
            OfferSide offerSide = offerSideMap.get(x);
            context.subscribeToBarsFeed(x, period, offerSide, listener);//逆市运行间隔
            context.subscribeToBarsFeed(x, Period.DAILY, offerSide, listener);//逆市运行间隔
            if (show_chart) {
                IChart chart = context.getChart(x);
                if (chart != null) {
                    IFeedDescriptor feedDescriptor = chart.getFeedDescriptor();
                    if (feedDescriptor != null) {
                        feedDescriptor.setPeriod(Period.DAILY);
                        feedDescriptor.setOfferSide(offerSide);
                        chart.setFeedDescriptor(feedDescriptor);
                        IIndicator tl_boll = indicators.getIndicator("TL_Boll");
                        if (tl_boll != null) {
                            chart.add(tl_boll, new Object[]{indiNums, nbDevUp, nbDevDn, maType.ordinal()});
                        }
                        chart.add(indicators.getIndicator("BBANDS"), new Object[]{indiNums, nbDevUp, nbDevDn, maType.ordinal()});
                        chart.add(indicators.getIndicator("ATR"), new Object[]{indiNums});
                        chart.repaint();
                    }
                }
            }
        });
        initEntity.r = true;
        //启动日志
        if (super.log_start_stop) {
            try {
                //遍历所有订单
                List<IOrder> orderList = engine.getOrders();
                for (BaseConfig baseConfig : initEntity.a) {
                    if (!baseConfig.isValid()) {
                        continue;
                    }
                    if (baseConfig instanceof ApolloConfig) {
                        ApolloConfig c = (ApolloConfig) baseConfig;
                        OrderExManager o = getOrderExManager(c, orderList, ApolloOrderComment.class);
                        if (o.buy_last != null) {
                            out("%s-[%d], %s, LONG  order.index=%d , amount=%s, last_price = %s, TP = %s , SL = %s , next_limit_price=%s ",
                                    c.name, c.index, c.instrument.name(),
                                    o.buy_last.seriesIndex,
                                    o.buy_last.order.getAmount(),
                                    ((ApolloOrderComment) o.buy_last.orderComment).pre_open_price,
                                    o.buy_last.order.getTakeProfitPrice(),
                                    o.buy_last.order.getStopLossPrice(),
                                    o.buyLimitList.isEmpty() ? 0 : o.buyLimitList.get(0).order.getOpenPrice());
                        } else {
                            out("%s-[%d], %s, LONG  has no order", c.name, c.index, c.instrument.name());
                        }
                        if (o.sell_last != null) {
                            out("%s-[%d], %s, SHORT order.index=%d , amount=%s, last_price = %s, TP = %s , SL = %s , next_limit_price=%s ",
                                    c.name, c.index, c.instrument.name(),
                                    o.sell_last.seriesIndex,
                                    o.sell_last.order.getAmount(),
                                    ((ApolloOrderComment) o.sell_last.orderComment).pre_open_price,
                                    o.sell_last.order.getTakeProfitPrice(),
                                    o.sell_last.order.getStopLossPrice(),
                                    o.sellLimitList.isEmpty() ? 0 : o.sellLimitList.get(0).order.getOpenPrice());
                        } else {
                            out("%s-[%d], %s, SHORT  has no order", c.name, c.index, c.instrument.name());
                        }
                    }
                }
            } catch (JFException e) {
                err("init error:", e.getMessage());
            }
        }


        try {
            long bartime = history.getPreviousBarStart(Period.DAILY,context.getTime());
            insMap.forEach((x,y)->{
                calcATR(x, Period.DAILY, bartime);
                calcBoll(x, Period.DAILY, bartime);
                calcYlzc(x, bartime);
            });
        } catch (JFException e) {
            context.stop();
        }
        mpm.add("init", begin, System.currentTimeMillis());
    }

    @Override
    public void onBar(Instrument instrument, Period period, OfferSide offerSide, IBar bar) {
        long time = bar.getTime();
        if (period.equals(Period.DAILY)) {
            dailyOpen(instrument, bar,time);
        }
        long begin = System.currentTimeMillis();
        ITimeDomain timeDomain0;
        ITimeDomain timeDomain1;
        try {
            timeDomain0 = dataService.getOfflineTimeDomain(0);
            timeDomain1 = dataService.getOfflineTimeDomain(-1);
        } catch (JFException e) {
            if (log_error) warn("onBar.getOfflineTimeDomain error:%s", e);
            return;
        }
        if (timeDomain0 == null || timeDomain1 == null) {
            if (log_error) warn("onBar.getOffline,TimeDomain is null");
            return;
        }

        //停盘时间不干活
        if (timeDomain1.getEnd() + period.getInterval() > time || time > timeDomain0.getStart() - period.getInterval()) {
            return;
        }
        timeDomain = timeDomain0;
        //逆势部分
        if (period.equals(this.period)) {
            configMap.entrySet().stream()
                    .filter(x -> x.getValue().isValid()
                            && x.getValue() instanceof ApolloConfig
                            && getName().equals(((ApolloConfig) x.getValue()).name)
                            && ((ApolloConfig) x.getValue()).instrument == instrument)
                    .forEach(x -> Rprocess((ApolloConfig) x.getValue(), bar.getTime()));
        }
        mpm.add("onBar", begin, System.currentTimeMillis());
    }

    //开盘时要干的事儿
    private void dailyOpen(Instrument instrument,IBar bar,long time) {
        long begin = System.currentTimeMillis();
        calcATR(instrument, Period.DAILY, time);
        calcBoll(instrument, Period.DAILY, time);
        calcYlzc(instrument, time);
        if (moveSL) {
            moveSLByDaily(instrument,bar);
        }
        mpm.add("dailyOpen", begin, System.currentTimeMillis());
    }

    //计算布林带
    private void calcBoll(Instrument instrument, Period daily, long time) {
        InstrumentEntity ie = insMap.get(instrument);
        double[][] bolls = null;
        try {
            bolls = indicators.bbands(instrument, daily, offerSideMap.get(instrument), appliedPrice, indiNums, nbDevUp, nbDevDn, maType, Filter.WEEKENDS, 1, time, 0);
        } catch (JFException e) {
            if (log_error) warn("get atr error:%s", e);
        }
        if (bolls != null && bolls.length == 3) {
            Boll boll = new Boll(bolls[0][0], bolls[1][0], bolls[2][0]);
            bollMap.put(instrument, boll);
            if (log_daily_open) out("%s.%d days boll=%s", instrument, indiNums, boll.toStringDiff(ie));
        }
    }

    //计算压力支撑位
    private void calcYlzc(Instrument instrument, long time) {
        //找前一天的压力支撑位
        YlzcMap ym = getYlzc(instrument, time, ylzcNums);
        ylzcMap.put(instrument, ym);
        if (log_ylzc) out("%s.ylw=%s", instrument.name(), ym.ylw);
        if (log_ylzc) out("%s.zcw=%s", instrument.name(), ym.zcw);
    }

    //计算波动率
    private void calcATR(Instrument instrument, Period period, long time) {
        InstrumentEntity ie = insMap.get(instrument);
        double[] atrs = null;
        try {
            atrs = indicators.atr(instrument, period, offerSideMap.get(instrument), indiNums, Filter.WEEKENDS, 1, time, 0);
        } catch (JFException e) {
            if (log_error) warn("%s get atr error:%s", instrument.name(), e);
        }
        if (atrs != null && atrs.length > 0 && atrs[0] != 0) {
            atrMap.put(instrument, atrs[0]);
            if (log_daily_open) out("%s.%d days atr=%s", instrument, indiNums, ie.formatPrice(atrs[0]));
        }
    }

    //开始运行
    private void Rprocess(ApolloConfig x, long time) {
        long begin = System.currentTimeMillis();
        //先做第一遍过滤，过滤太长的时间，过滤点差,过滤找不到压力支撑位，波动率，布林值等等因素,这些事理论上来说都是不存在的
        Instrument instrument = x.instrument;
        InstrumentEntity ie = insMap.get(instrument);
        if (ie == null) {
            return;
        }
        if (!atrMap.containsKey(instrument) || atrMap.get(instrument) == null) {
            if (log_order_reason) out("%s can't find atr with %s", instrument.name(), atrMap.toString());
        }
        if (!bollMap.containsKey(instrument) || bollMap.get(instrument) == null) {
            if (log_order_reason) out("%s can't find boll with %s", instrument.name(), bollMap.toString());
        }
        if (!ylzcMap.containsKey(instrument) || ylzcMap.get(instrument) == null) {
            if (log_order_reason) out("%s can't find ylzc with %s", instrument.name(), ylzcMap.toString());
        }
        ITick tick;
        try {
            tick = history.getLastTick(instrument);
        } catch (JFException e) {
            if (log_error) warn("%s get Last Tick error", instrument.name());
            return;
        }
        if (tick == null) {
            return;
        }
        long time_diff = time - tick.getTime();
        if ((time - tick.getTime()) > 2 * period.getInterval()) {
            if (log_order_reason)
                out("%s,tick time=%s,time.diff=%d s > %d s,can't open order", instrument.name(), CalendarUtils.formatStandard(tick.getTime()), time_diff / 1000, (2 * period.getInterval() / 1000));
            return;
        }
        //1.找出所有的订单
        List<IOrder> orderList;
        try {
            orderList = engine.getOrders(instrument);
        } catch (JFException e) {
            if (log_order_reason) warn("%s getOrders error:%s", instrument, e != null ? e.getMessage() : "null");
            return;
        }

        OrderExManager orderExManager = getOrderExManager(x, orderList, ApolloOrderComment.class);
        if (x.tradeDirection.isLong() || x.tradeDirection == TradeDirection.BOTH) {
            RprocessByType(x, ie, tick, TradeDirection.LONG, orderExManager);
        }
        if (x.tradeDirection == TradeDirection.SHORT || x.tradeDirection == TradeDirection.BOTH) {
            RprocessByType(x, ie, tick, TradeDirection.SHORT, orderExManager);
        }
        mpm.add("process", begin, System.currentTimeMillis());
    }

    //根据固定类别进行开单交易
    private void RprocessByType(ApolloConfig x, InstrumentEntity ie, ITick tick, TradeDirection tradeDirection, OrderExManager orderExManager) {
        long begin = System.currentTimeMillis();
        long time = context.getTime();
        List<OrderEx> orderExList = orderExManager.getOrderExList(tradeDirection);
        List<OrderEx> orderLimitExList = orderExManager.getLimitOrderExList(tradeDirection);
        OrderEx last = orderExManager.getLast(tradeDirection);
        OrderEx first = orderExManager.getFirst(tradeDirection);
        OrderEx reflectLast = orderExManager.getReflectLastOrderEx(tradeDirection);

        //如果有限价单,不做任何处理
        if (!orderLimitExList.isEmpty()) {
            return;
        }

        //如果空仓则开首仓
        if (orderExList.isEmpty() && R_Trade) {
            openFirstOrder(x, tick, tradeDirection, reflectLast);
            return;
        }

        //如果当前有两仓，就不对了，表示没有合并仓位，需要合并仓位
        if (orderExList.size() > 1) {
            merge(x.instrument, ie, x.name, x.index, tradeDirection);
            return;
        }

        //判断有一个正常的仓位
        boolean is_normal = last != null && first != null && last.equals(first) && last.orderComment.type == OpenType.P && last.orderComment instanceof ApolloOrderComment;


        //如果上一仓不为null，并且上一仓是当前序列里唯一的一仓，并且上一仓属于普通加仓
        if (is_normal) {
            if ((tradeDirection.isLong() && tick.getAsk() < last.order.getOpenPrice())
                    || (tradeDirection == TradeDirection.SHORT && tick.getBid() > last.order.getOpenPrice())
            ) {
                ApolloOrderComment apolloOrderComment = (ApolloOrderComment) last.orderComment;
                int pause_time = apolloOrderComment.next_add_pause_time;//加仓数列里期望暂停的时间
                long exp_pause_time = pause_time * 60000;//实际上期望暂停的时间（已经加上了停盘时间）
                long act_pause_time = time - last.order.getFillTime();//已经暂停的时间
                if (act_pause_time > exp_pause_time) {
                    addOrder(x, ie, tradeDirection, tick, last, reflectLast);//加仓
                }
            }
        }

        if (is_normal && moveSL) {
            if ((tradeDirection.isLong() && tick.getBid() > last.order.getOpenPrice() && last.order.getStopLossPrice() < last.order.getOpenPrice())
                    || (tradeDirection.isShort() && tick.getAsk() < last.order.getOpenPrice() && last.order.getStopLossPrice() > last.order.getOpenPrice())) {
                try {
                    baoben(x, ie, tradeDirection, last, tick);
                } catch (JFException e) {
                    err("updateStopLoss error:s", e.getMessage());
                }
            }
        }
        mpm.add("processByType", begin, System.currentTimeMillis());
    }

    //每天移动止损
    private void moveSLByDaily(Instrument instrument,IBar bar) {
        ITimeDomain timeDomain0;
        ITimeDomain timeDomain1;
        try {
            timeDomain0 = dataService.getOfflineTimeDomain(0);
            timeDomain1 = dataService.getOfflineTimeDomain(-1);
        } catch (JFException e) {
            if (log_error) warn("onBar.getOfflineTimeDomain error:%s", e);
            return;
        }
        if (timeDomain0 == null || timeDomain1 == null) {
            if (log_error) warn("onBar.getOffline,TimeDomain is null");
            return;
        }
        //停盘时间不干活
        if (timeDomain1.getEnd() > context.getTime() || context.getTime() > timeDomain0.getStart()) {
            return;
        }

        double atr = atrMap.get(instrument);
        if(Math.abs(bar.getHigh()-bar.getLow()) < atr/2){
            return;
        }
        try {
            List<IOrder> orderList = engine.getOrders(instrument);
            for (IOrder order : orderList) {
                if (!order.getLabel().startsWith(getName())) {
                    continue;
                }
                if (order.getOrderCommand() == IEngine.OrderCommand.BUY && order.getStopLossPrice() >= order.getOpenPrice()) {
                    //开始移动
                    if(bar.getClose() > bar.getOpen()
                            && bar.getHigh() -  bar.getClose() < bar.getClose() - bar.getOpen()
                            && bar.getLow() > order.getStopLossPrice() ){
                        if(log_order_move){
                            out("%s move sl from [%s] to [%s]",order.getLabel(),order.getStopLossPrice(),bar.getLow());
                        }
                        order.setStopLossPrice(bar.getLow(),OfferSide.BID,trailingStep);
                    }

                    if(bar.getOpen() > bar.getClose()
                        || bar.getHigh()- Math.max(bar.getOpen(),bar.getClose()) > Math.abs(bar.getOpen()-bar.getClose())){
                        if(log_order_move){
                            out("%s move tp from [%s] to [%s]",order.getLabel(),order.getTakeProfitPrice(),bar.getHigh());
                        }
                        order.setTakeProfitPrice(bar.getHigh());
                    }
                    continue;
                }

                if (order.getOrderCommand() == IEngine.OrderCommand.SELL && order.getStopLossPrice() <= order.getOpenPrice()) {
                    //开始移动
                    if(bar.getClose() < bar.getOpen()
                            && bar.getClose() -  bar.getLow() < bar.getOpen() - bar.getClose()
                            && bar.getHigh() < order.getStopLossPrice() ){
                        if(log_order_move){
                            out("%s move sl from [%s] to [%s]",order.getLabel(),order.getStopLossPrice(),bar.getHigh());
                        }
                        order.setStopLossPrice(bar.getHigh(),OfferSide.ASK,trailingStep);
                    }

                    if(bar.getOpen() < bar.getClose()
                            || Math.min(bar.getOpen(),bar.getClose()) - bar.getLow() > Math.abs(bar.getOpen()-bar.getClose())){
                        if(log_order_move){
                            out("%s move tp from [%s] to [%s]",order.getLabel(),order.getTakeProfitPrice(),bar.getLow());
                        }
                        order.setTakeProfitPrice(bar.getLow());
                    }
                }
            }
        } catch (JFException e) {
            if (log_order_reason) warn("%s getOrders error:%s", instrument, e != null ? e.getMessage() : "null");
            return;
        }

    }

    //保本止损
    private void baoben(
            ApolloConfig x,      //配置
            InstrumentEntity ie, //交易品种属性
            TradeDirection tradeDirection,
            OrderEx<ApolloOrderComment> last,      //尾仓
            ITick tick
    ) throws JFException {
        //计算理论的回调价格
        double lowhigh = tradeDirection == TradeDirection.LONG ? getHighPrice(x.instrument, last.order.getFillTime(), period) : getLowPrice(x.instrument, last.order.getFillTime(), period);
        if (tradeDirection.isLong()) {
            if (lowhigh >= last.orderComment.tp) {
                if(log_order_move){
                    out("%s,lowhigh=%s, last.orderComment.tp=%s, move sl from [%s] to [%s]",last.order.getLabel(),lowhigh,last.orderComment.tp,last.order.getStopLossPrice(),last.order.getOpenPrice());
                }
                last.order.setStopLossPrice(last.order.getOpenPrice(), OfferSide.BID, trailingStep);
            }
        } else {
            if (lowhigh <= last.orderComment.tp) {
                last.order.setStopLossPrice(last.order.getOpenPrice(), OfferSide.ASK, trailingStep);
            }
        }
    }

    //开首仓
    private void openFirstOrder(
            ApolloConfig x,     //配置
            ITick tick,         //最后一次报价
            TradeDirection tradeDirection,    //交易方向
            OrderEx reflectLast   //反向订单尾仓
    ) {
        //如果上一次止损了，那么必须产生新的压力支撑位
        if (last_sl_new_ylzc) {
            IOrder order = getLastOrder(x, tradeDirection, 60);
            if (order != null) {
                if (order.getProfitLossInUSD() < 0) {
                    //在首仓开单时间到当前时间之间，查找压力支撑位,找到才能继续做单
                    YlzcDTO ylzc = findYlzc(order.getCloseTime(), x.instrument, tradeDirection);
                    if (ylzc == null) {
                        return;
                    }
                }
            }
        }

        long begin = System.currentTimeMillis();
        //step.1 获取ATR,BOLL
        Instrument instrument = x.instrument;
        InstrumentEntity ie = insMap.get(instrument);
        if (ie == null) {
            return;
        }
        double atr = atrMap.get(instrument); //atr
        Boll boll = bollMap.get(instrument); //布林带
        double total_pips = atr * x.atr + boll.diff * x.boll;//计算总抗单点位,(ATR的X倍+布林差的Y倍)
        int total_pips_int = (int) (total_pips * ie.pipMulti);//整数位的抗单距离
        //step.2 获取加仓数列
        Series series = asm.getSeriesByValue(total_pips_int, maxATRSeries);//根据总抗单点位 获取ATR 所对应的加仓数列
        if (series == null || series.get(0) == null || series.size() <= first_loss_limit_position + 1) {
            if (log_order_reason) {
                out("%s,can't open first order by no find series with [atr=%s * %s+boll=%s * %s]=%d by \n%s",
                        instrument.name(), x.atr, ie.formatPrice(atr), x.boll, ie.formatPrice(boll.diff), total_pips_int, asm.toString());
            }
            return;
        }
        ForexConfigSeriesDTO sm0 = series.get(0);
        if (sm0 == null) {
            return;
        }
        int first_loss_pip = 0;
        for (int i = 1; i <= first_loss_limit_position; i++) {
            first_loss_pip = series.get(i).addpip;
        }
        //step.3 找到压力位和支撑位
        double currentPrice = tradeDirection.isLong() ? tick.getAsk() : tick.getBid();//当前价格
        double calc_ylzc_price = tradeDirection.isLong() ? currentPrice - first_loss_pip * ie.pipValue : currentPrice + first_loss_pip * ie.pipValue;//计算压力支撑位的真实价格
        YlzcMap ym = ylzcMap.get(instrument);
        YlzcDTO ylzc = getFirstYlzcByLevel(calc_ylzc_price, stop_loss_level, tradeDirection, tradeDirection.isLong() ? ym.zcw : ym.ylw);//根据当前价格找到第一距离最近的压力支撑位
        if (ylzc == null) {
            if (log_order_reason)
                out("can't open order with no find level-%d %s", stop_loss_level, tradeDirection.isLong() ? "zcw" : "ylw");
            return;
        }
        if (log_ylzc)
            out("config[%d] %s.%s open first order calc_ylzc_price=%s,ylzc=%s", x.index, instrument.name(), tradeDirection.name(), ie.formatPrice(calc_ylzc_price), ylzc.toString());

        //step.4
        double distance_real = Math.abs(currentPrice - ylzc.value);//实际上到达止损位的距离
        double open_price = 0;
        double distance_exp = series.totalPip * ie.pipValue; //期望的抗单距离
        if (distance_real > distance_exp) {
            return;
        }
        IEngine.OrderCommand orderCommand = tradeDirection.isLong() ? IEngine.OrderCommand.BUY : IEngine.OrderCommand.SELL;

        //step.5 如果找到了止损的压力支撑位，开始设置止损价和止盈价
        double sl = tradeDirection.isLong() ? new BigDecimal(ylzc.value - stop_loss_atr * atr).setScale(ie.scale, RoundingMode.DOWN).doubleValue() : new BigDecimal(ylzc.value + stop_loss_atr * atr).setScale(ie.scale, RoundingMode.UP).doubleValue();
        double tp = tradeDirection.isLong() ? new BigDecimal(currentPrice + sm0.backpip * ie.pipValue).setScale(ie.scale, RoundingMode.UP).doubleValue() : new BigDecimal(currentPrice - sm0.backpip * ie.pipValue).setScale(ie.scale, RoundingMode.DOWN).doubleValue();

        int next_add_pause_time = sm0.time;//下次强制加仓的点位
        double reflect = 1;
        if (reflectManager.valid && reflectLast != null) {
            reflect *= reflectManager.get(reflectLast.seriesIndex);
        }
        double amount = Math.max(ie.minAmount, ie.minAmount * x.m * reflect);//首仓直接就是用最小仓位*倍率*反向单倍率
        int first_equit = (int) account.getEquity();
        ApolloOrderComment orderComment = new ApolloOrderComment(series.seriesId, 0, OpenType.P, first_equit, context.getTime(), 0, ylzc.value, tp, currentPrice, currentPrice, currentPrice, next_add_pause_time);
        OrderEx orderEx = new OrderEx(x.name, LabelType.M, x.index, 0, instrument, tradeDirection);
        try {
            IOrder order = engine.getOrder(orderEx.toString());
            if (order != null) {
                return;
            }
        } catch (JFException e) {
            err("engine.getOrder error:%s", e.getMessage());
        }
        String comment = orderComment.toString();
        Date firstTime = CalendarUtils.getFirstTime(new Date(context.getTime()), cn.skyquant.quant4j.sdk.util.time.Period.daily);
        long goodTillTime = orderCommand.isConditional() ? firstTime.getTime() + 86460000 : 0;

        try {
            if (log_order_reason) {
                out("ready to submit first order,[%d-%d]-%s.%s,amount=%s,ylzc=%s,sl=%s,tp=%s,%s", series.seriesId, 0, x.instrument.name(), tradeDirection.name(), amount, ylzc.value, sl, tp, comment);
            }
            engine.submitOrder(orderEx.toString(), x.instrument, orderCommand, amount, open_price, 0, sl, tp, goodTillTime, comment);
        } catch (JFException e) {
            warn("config[%d] %s open first order error:%s", x.index, instrument, e.getMessage());
        }
        mpm.add("openFirstOrder", begin, System.currentTimeMillis());
    }

    /**
     * 加仓
     *
     * @param x           apollo配置
     * @param ie          交易品种属性
     * @param tick        最后一次报价
     * @param last        当前尾仓
     * @param reflectLast 反向单尾仓
     */
    private void addOrder(
            ApolloConfig x,      //配置
            InstrumentEntity ie, //交易品种属性
            TradeDirection tradeDirection,
            ITick tick,         //最后一次报价
            OrderEx<ApolloOrderComment> last,       //尾仓
            OrderEx<ApolloOrderComment> reflectLast //反向单尾仓
    ) {
        long begin = System.currentTimeMillis();
        Instrument instrument = x.instrument;
        int index = last.seriesIndex + 1;
        //step.1 获取ATR,BOLL
        double atr = atrMap.get(instrument); //atr
        Boll boll = bollMap.get(instrument); //布林带
        double total_pips = atr * x.atr + boll.diff * x.boll;//计算总抗单点位,(ATR的X倍+布林差的Y倍)
        int total_pips_int = (int) (total_pips * ie.pipMulti);//整数位的抗单距离
        //step.2 获取加仓数列
        Series series = asm.getSeriesByValue(total_pips_int, maxATRSeries);//根据总抗单点位 获取ATR 所对应的加仓数列
        if (series == null) {
            if (log_order_reason)
                out("%s,can't open %d order by no find series with [atr=%s * %s,boll=%s * %s] by \n%s", instrument, index, x.atr, ie.formatPrice(atr), x.boll, ie.formatPrice(boll.diff), asm.toString());
            return;
        }
        if (index < 0 || index >= series.list.size()) {
            if (log_order_reason)
                out("%s,index=%d >=series[%d].size=%d,can't open order", instrument, index, series.seriesId, series.list.size());
            return;
        }
        ForexConfigSeriesDTO sm = series.list.get(index);                                             //本次加仓数列行

        //step.3 计算开仓时的价格,市价单为0
        double current_price = tradeDirection.isLong() ? tick.getAsk() : tick.getBid();                 //当前价格
        IEngine.OrderCommand orderCommand = tradeDirection.isLong() ? orderCommand = IEngine.OrderCommand.BUYLIMIT : IEngine.OrderCommand.SELLLIMIT;
        double last_price = last.orderComment.open_price;//最后一仓开仓的价格
        int add_pip = sm.addpip;//当前仓位要求的加仓点距
        double open_price = tradeDirection.isLong() ?
                new BigDecimal(last_price - add_pip * ie.pipValue).setScale(ie.scale, RoundingMode.DOWN).doubleValue() :
                new BigDecimal(last_price + add_pip * ie.pipValue).setScale(ie.scale, RoundingMode.UP).doubleValue();//开仓价
        //真实开仓价格必须在首仓找到的压力支撑位以上
        double ylzc = last.orderComment.ylzc;
        if (tradeDirection.isLong() && open_price < ylzc) {
            if (log_order_reason)
                out("config[%d] %s open %d LONG order, last_price=%s, add_pip=%d,open_price=%s < ylzc:%s ,can't add order", x.index, instrument.name(), index, last_price, add_pip, open_price, ylzc);
            return;
        }
        if (tradeDirection.isShort() && open_price > ylzc) {
            if (log_order_reason)
                out("config[%d] %s open %d SHORT order, last_price=%s, add_pip=%d,open_price=%s > ylzc:%s ,can't add order", x.index, instrument.name(), index, last_price, add_pip, open_price, ylzc);
            return;
        }

        //step.4 计算上一仓到达开仓点位后要亏损的钱
        double last_order_amount = last.order.getAmount();//上一仓数量
        double last_avg_price = last.order.getOpenPrice();//上一仓的平均价格
        double loss_money = ie.getMoney(tradeDirection.isLong() ? open_price - last_avg_price : last_avg_price - open_price, last_order_amount);
        double loss_charge = ie.getCharge(last_order_amount);
        double real_total_loss = loss_money - loss_charge;//真实亏损

        //step.5 最复杂的一步，根据盈利钱来倒推计算仓位
        //计算一下要盈利这么多钱，需要多少仓位,先计算需要几个最小仓位
        //原油 1个点 1块钱
        //货币 1个点 0.1块钱
        //黄金 1个点 0.01块钱
        //回调点位15 *  (仓位2=0.002 / 最小仓位1=0.001) * 每个仓位能盈利的钱(0.1) = 3(总盈利的钱)
        //(仓位2=0.002 / 最小仓位1=0.001) = 3/0.1/15 = 2 2就是2倍最小仓位
        //实际仓位 = 仓位倍率*最小仓位 = 3/0.1/15*0.001
        double reflect = 1;
        if (reflectManager.valid && reflectLast != null && reflectLast.order.getProfitLossInUSD() < 0) {
            reflect *= reflectManager.get(reflectLast.seriesIndex);
        }
        double tp_money = sm.tp * x.m * reflect;//本次订单希望盈利这么多钱
        double money_diff = tp_money - real_total_loss;//希望盈利的钱-真实亏损的钱= 真正要盈利的钱
        //12.56/0.1/70*0.001 = 125.6/70
        double amount_exp = money_diff / ie.minMoney / sm.backpip * ie.minAmount;//要有这么多仓位，才能在回调这么多点位时，赚到这么多钱
        double amount = new BigDecimal(amount_exp - last_order_amount).setScale(ie.amountScale, RoundingMode.HALF_UP).doubleValue();//期望的点位-实际的点位=要开仓的点位
        //做一个四舍五入,怕首仓时不够
        if (amount < ie.minAmount) {
            amount = ie.minAmount;
        }
        if (log_order_reason) {
            out("backpip=%d,open_price=%s,current_price=%s,amount=%s,real_total_loss=%.2f,money_diff=%.2f,tp_money=%.0f,last_avg_price=%s,last_order_amount=%s,loss_money=%.2f,amount_exp=%s",
                    sm.backpip, open_price, current_price, amount, real_total_loss, money_diff, tp_money, last_avg_price, last_order_amount, loss_money, ie.formatAmout(amount_exp));
        }

        //step.6 开仓
        OrderEx orderEx = new OrderEx(x.name, LabelType.N, x.index, index, x.instrument, tradeDirection);
        String label = orderEx.toString();
        try {
            if (engine.getOrder(label) != null) {
                if (log_order_reason) {
                    out("order.label[%s] exist ,can't open order", label);
                }
                return;
            }
        } catch (JFException e) {
            err("getOrder error:%s", e.getMessage());
        }

        double tp = tradeDirection.isLong() ? new BigDecimal(open_price + sm.backpip * ie.pipValue).setScale(ie.scale, RoundingMode.UP).doubleValue() : new BigDecimal(open_price - sm.backpip * ie.pipValue).setScale(ie.scale, RoundingMode.DOWN).doubleValue();

        ApolloOrderComment orderComment = new ApolloOrderComment(series.seriesId, index, OpenType.P, last.orderComment.first_equit, last.orderComment.first_time.getTime(), (int) real_total_loss, ylzc, tp, last.orderComment.first_open_price, last.orderComment.open_price, open_price, 0);
        String comment = orderComment.toString();
        try {
            long goodTillTime = history.getNextBarStart(Period.DAILY, context.getTime()) - period.getInterval() / 2;
            //最多挂单一天
            if (log_order_submit) {
                out("ready to submit order,label=%s,%s [%d-%d]-%s,amount=%s,currentPrice=%s,open_price=%s, tp=%s, %s",
                        label, orderCommand.name(), series.seriesId, index, x.instrument.name(), amount, current_price, open_price,tp, comment);
            }
            engine.submitOrder(label, x.instrument, orderCommand, amount, open_price, slippage, 0, 0, goodTillTime, comment);
        } catch (JFException e) {
            if (log_error)
                warn("[label=%s,current_price=%s,open_price=%s,amount=%s],error=%s", label, current_price, open_price, ie.formatAmout(amount), e.getMessage());
        }

        mpm.add("addOrder", begin, System.currentTimeMillis());
    }

    /**
     * 整个流程
     * 1. M首仓(带sl,tp) -> 2.N加仓(sl0,tp0) -> 3.修改M仓(sl0,tp0) -> 4.合并同类订单(计算压力支撑位),生成新M - >5.修改新M仓止盈止损价
     */
    @Override
    public void onMessageEx(IMessage message) {
        IOrder order = message.getOrder();
        if (order == null) {
            return;
        }
        switch (message.getType()) {
            case ORDER_FILL_REJECTED:
            case ORDER_SUBMIT_REJECTED:
            case ORDERS_MERGE_REJECTED:
            case ORDER_CHANGED_REJECTED:
                if (log_order_reason) err(message.toString());
                break;
        }
        String label = order.getLabel();
        if (label == null) {
            return;
        }
        String[] label_arr = label.split("_");
        if (label_arr.length < 1) {
            return;
        }
        if (getName().equalsIgnoreCase(label_arr[0])) {
            OrderEx<ApolloOrderComment> orderEx = new OrderEx(order, new ApolloOrderComment());
            //监听逆势仓的单子
            if (!orderEx.valid(getName())) {
                return;
            }
            ROnMessage(message, orderEx, order);
        }
    }

    private void ROnMessage(IMessage message, OrderEx<ApolloOrderComment> orderEx, IOrder order) {
        long begin = System.currentTimeMillis();
        //获取订单的基本参数
        InstrumentEntity ie = new InstrumentEntity(order.getInstrument());
        IAccount account = this.context.getAccount();
        ApolloOrderComment orderComment = orderEx.orderComment;
        int first_equit = orderComment.first_equit;
        int current_equity = (int) account.getEquity();
        int profit = (int) (account.getEquity() - orderComment.first_equit);
        int index = orderComment.series_index;
        int seriesId = orderComment.series_id;
        LabelType labelType = orderEx.labelType;
        double openPrice = order.getOpenPrice();
        double closePrice = order.getClosePrice();
        double amount = order.getAmount();
        IEngine.OrderCommand orderCommand = order.getOrderCommand();
        String instrumentName = order.getInstrument().name();
        double sl = order.getStopLossPrice();
        double tp = order.getTakeProfitPrice();
        int dist = Math.abs(ie.priceToPip(orderEx.orderComment.first_open_price - orderEx.orderComment.open_price));
        switch (message.getType()) {
            case ORDER_FILL_OK:
                if (log_order_fill) {
                    if (sl != 0 && tp != 0) {
                        notif("Apollo, [%d-%5d]=%5d,Fill %d-%d Order[%s.%s,amount=%s,open=%s,sl=%s,tp=%s,pause=%d,type=%s]",
                                current_equity, first_equit, profit, seriesId, index, instrumentName, orderCommand, amount, openPrice, sl, tp, orderComment.next_add_pause_time, orderComment.type);
                    } else {
                        notif("Apollo, [%d-%5d]=%5d,Fill %d-%d Order[%s.%s,amount=%s,open=%s,pause=%d,type=%s]",
                                current_equity, first_equit, profit, seriesId, index, instrumentName, orderCommand, amount, openPrice, orderComment.next_add_pause_time, orderComment.type);
                    }
                }
                //如果是加仓订单成交，则修改止盈止损值
                if (labelType == LabelType.N) {
                    cancelSLAndTP(orderEx);
                }

                mpm.add("ROnMessage.ORDER_FILL_OK", begin, System.currentTimeMillis());
                break;
            case ORDER_CLOSE_OK:
                if (order.getState() == IOrder.State.CLOSED) {
                    closeLimit(orderEx);
                    if (log_order_merge && message.getReasons().contains(IMessage.Reason.ORDER_CLOSED_BY_MERGE)) {
                        out("Apollo, [%d-%5d]=%5d,Close by merge %d-%d Order[%s.%s ,amount=%s,open=%s]",
                                current_equity, first_equit, profit, seriesId, index, instrumentName, orderCommand, amount, openPrice);
                    }
                    if (order.getProfitLossInUSD() > 0) {
                        if (log_order_close) {
                            info("Apollo, [%d-%5d]=%5d,Close by TP %d-%d Order[%s.%s ,amount=%s,open=%s,sl=%s,tp=%s,close=%s],profit=%s",
                                    current_equity, first_equit, profit, seriesId, index, instrumentName, orderCommand, amount, openPrice, sl, tp, closePrice, order.getProfitLossInUSD());
                        }
                        CloseRecordDTO closeRecordDTO = new CloseRecordDTO(orderEx, CloseType.TP, context.getTime());
                        super.addStatistics(orderEx.orderComment.first_time.getTime(), context.getTime(), first_equit, current_equity, index, order.getProfitLossInUSD(), amount, dist);
                        lastCloseOrder.put(closeRecordDTO.key, order);//记录最后一个平仓订单
                    }
                    if (order.getProfitLossInUSD() < 0) {
                        if (log_order_close) {
                            err("Apollo, [%d-%5d]=%5d,Close by SL %d-%d Order[%s.%s ,amount=%s,open=%s,sl=%s,tp=%s,close=%s],profit=%s",
                                    current_equity, first_equit, profit, seriesId, index, instrumentName, orderCommand, amount, openPrice, sl, tp, closePrice, order.getProfitLossInUSD());
                        }
                        CloseRecordDTO closeRecordDTO = new CloseRecordDTO(orderEx, CloseType.SL, context.getTime());
                        super.addStatistics(orderEx.orderComment.first_time.getTime(), context.getTime(), first_equit, current_equity, index, order.getProfitLossInUSD(), amount, dist);
                        lastCloseOrder.put(closeRecordDTO.key, order);//记录最后一个平仓订单

                    }
                } else if (order.getState() == IOrder.State.CANCELED) {
                    if (log_order_cancel) {
                        out("Apollo, Cancel Order %d-%d %s [%s]", orderEx.orderComment.series_id, orderEx.seriesIndex, orderCommand.name(), order.getComment());
                    }
                }
                mpm.add("ROnMessage.ORDER_CLOSE_OK", begin, System.currentTimeMillis());
                break;
            case ORDERS_MERGE_OK:
                if (log_order_merge) {
                    out("Apollo, Merge Order[%s.%s,amount=%s,avg_open_price=%s] comment=%s", instrumentName, orderCommand, amount, openPrice, order.getComment());
                }
                double ylzc = orderEx.orderComment.ylzc;//首仓记录的压力支撑价
                double atr = atrMap.get(order.getInstrument());

                double newSL = orderCommand == IEngine.OrderCommand.BUY ? new BigDecimal(ylzc - atr * stop_loss_atr).setScale(ie.scale, RoundingMode.DOWN).doubleValue() : new BigDecimal(ylzc + atr * stop_loss_atr).setScale(ie.scale, RoundingMode.UP).doubleValue();
                double open_price = orderEx.orderComment.open_price;//开仓价
                Series series = asm.getSeriesById(orderEx.orderComment.series_id);
                if (series == null) {
                    return;
                }
                ForexConfigSeriesDTO sm = series.get(orderEx.seriesIndex);
                if (sm == null) {
                    break;
                }
                double tp_price = sm.backpip * ie.pipValue;
                double newTP = orderCommand == IEngine.OrderCommand.BUY ? new BigDecimal(open_price + tp_price).setScale(ie.scale, RoundingMode.UP).doubleValue() : new BigDecimal(open_price - tp_price).setScale(ie.scale, RoundingMode.DOWN).doubleValue();
                OfferSide offerSide = orderCommand == IEngine.OrderCommand.BUY ? OfferSide.ASK : OfferSide.BID;
                try {
                    order.setStopLossPrice(newSL, offerSide);
                    if (!moveSL) {
                        order.setTakeProfitPrice(newTP);
                    }
                } catch (JFException e) {
                    err("order.setTakeProfitPrice error:%s", e.getMessage());
                }
                if (log_order_merge) {
                    out("Apollo, Merge Order[%s.%s,amount=%s,avg_open_price=%s,sl=%s,tp=%s,loss=%s],comment=%s", instrumentName, orderCommand, amount, openPrice, newSL, newTP, order.getProfitLossInUSD(), order.getComment());
                }
                mpm.add("ROnMessage.ORDERS_MERGE_OK", begin, System.currentTimeMillis());
                break;
            case ORDER_CHANGED_OK:
                //修改止盈止损价成功,判断是不是M仓的止盈止损价都修改为0了
                if (orderEx.labelType == LabelType.M && sl == 0 && tp == 0) {
                    if (log_order_merge) {
                        out("Apollo, %d-%d,Merge sl and tp by Order[%s.%s labelType=%s] ", seriesId, index, instrumentName, orderCommand, orderEx.labelType);
                    }
                    //进行合并仓位
                    merge(order.getInstrument(), ie, orderEx.name, orderEx.configIndex, orderEx.tradeDirection);
                }
                mpm.add("ROnMessage.ORDER_CHANGED_OK", begin, System.currentTimeMillis());
                break;
            case ORDER_SUBMIT_OK:
                if (log_order_submit) {
                    ITick tick;
                    try {
                        tick = history.getLastTick(order.getInstrument());
                        double currentPrice = orderEx.tradeDirection.isLong() ? tick.getAsk() : tick.getBid();
                        out("Apollo, Submit %d-%d Order[%s.%s ,amount=%s,current_price=%s,open_price=%s,goodTillTime=%s],id=%s,label=%s",
                                seriesId, index, instrumentName, orderCommand, amount, currentPrice, order.getOpenPrice(), order.getGoodTillTime() == 0 ? "0" : CalendarUtils.formatStandard(order.getGoodTillTime()), order.getId(), order.getLabel());

                    } catch (JFException e) {
                        err("history.getLastTick error:%s", e.getMessage());
                    }
                }
                mpm.add("ROnMessage.ORDER_SUBMIT_OK", begin, System.currentTimeMillis());
                break;
            default:
                break;
        }
    }

    //找历史订单
    private IOrder getLastOrder(ApolloConfig x, TradeDirection td, int histdays) {
        String key = CloseRecordDTO.getKey(x.name, x.index, x.instrument, td);
        IOrder order = lastCloseOrder.get(key);
        if (order == null) {
            //找历史订单
            try {
                List<IOrder> orderList = history.getOrdersHistory(x.instrument, context.getTime() - Period.DAILY.getInterval() * histdays, context.getTime());//找到60天的历史历史订单
                List<IOrder> histList = orderList.stream()
                        .filter(o -> o.getProfitLossInUSD() != 0
                                && !o.getCloseHistory().isEmpty()
                                && QuantUtil.isMatch(td, o.getOrderCommand())
                                && new OrderEx(o, new ApolloOrderComment()).valid(x.name, x.index))
                        .collect(Collectors.toList());
                if (!histList.isEmpty()) {
                    order = histList.get(histList.size() - 1);
                    lastCloseOrder.put(key, order);
                }
            } catch (JFException e) {
                return null;
            }
        }
        return order;
    }

    //2.修改M仓
    private void cancelSLAndTP(OrderEx orderExNew) {
        //获取同类的M仓
        List<IOrder> orderList;
        try {
            orderList = engine.getOrders(orderExNew.instrument);
        } catch (JFException e) {
            return;
        }
        if (orderList == null || orderList.isEmpty()) {
            return;
        }
        List<OrderEx> sameMList = getSameOrders(orderList, orderExNew.instrument, orderExNew.name, orderExNew.configIndex, orderExNew.tradeDirection, LabelType.M, ApolloOrderComment.class);
        updateSLTP(sameMList, 0, 0);//将所有价格修改为0
    }

    //3.合并N和M仓
    private void merge(Instrument instrument, InstrumentEntity ie, String name, int configIndex, TradeDirection tradeDirection) {
        List<IOrder> orderList;
        try {
            orderList = engine.getOrders(instrument);
        } catch (JFException e) {
            return;
        }
        if (orderList == null || orderList.isEmpty()) {
            return;
        }
        List<OrderEx> mergeOrderExList = getSameOrders(orderList, instrument, name, configIndex, tradeDirection, null, ApolloOrderComment.class);
        List<OrderEx> invalidOrderList = mergeOrderExList.stream()
                .filter(x -> x.order.getTakeProfitPrice() != 0 || x.order.getStopLossPrice() != 0)
                .collect(Collectors.toList());
        if (!invalidOrderList.isEmpty()) {
            updateSLTP(invalidOrderList, 0, 0);
            return;
        }
        List<IOrder> mergeOrderList = mergeOrderExList.stream()
                .filter(x -> x.order.getTakeProfitPrice() == 0 && x.order.getStopLossPrice() == 0)
                .map(x -> x.order)
                .collect(Collectors.toList());
        //如果需要合并的订单只有1个，则放弃merge
        if (mergeOrderExList.size() < 2) {
            return;
        }

        OrderEx<ApolloOrderComment> last_N = mergeOrderExList.get(mergeOrderExList.size() - 1); //N仓（最新的仓）

        OrderEx<ApolloOrderComment> orderEx = new OrderEx(last_N.name, LabelType.M, last_N.configIndex, last_N.seriesIndex, last_N.instrument, last_N.tradeDirection);
        String label = orderEx.toString();
        try {
            if (engine.getOrder(label) != null) {
                return;
            }
        } catch (JFException e) {
            err("engine.getOrder error:%s", e.getMessage());
        }
        int series_index = last_N.orderComment.series_index;
        int series_id = last_N.orderComment.series_id;

        Series series = asm.getSeriesById(series_id);
        if (series == null || series.get(series_index) == null) {
            return;
        }

        ForexConfigSeriesDTO sm_next = series.get(series_index + 1);

        //需要自己算的就是跟下一仓有关的
        int first_equit = last_N.orderComment.first_equit;
        Date first_time = last_N.orderComment.first_time;
        int this_profitloss = last_N.orderComment.this_profitloss;
        double ylzc = last_N.orderComment.ylzc;
        double tp = last_N.orderComment.tp;
        double first_open_price = last_N.orderComment.first_open_price;
        double pre_open_price = last_N.orderComment.pre_open_price;
        double current_open_price = last_N.order.getOpenPrice();

        int offline_stop = 0;
        int next_add_pause_time = sm_next == null ? 0 : sm_next.time;//下次加仓暂停的时间
        if (sm_next != null) {
            next_add_pause_time = next_add_pause_time * getPauseTimeByYlzc(ie, last_N.orderComment.pre_open_price, current_open_price, tradeDirection);
            //判断在这个暂停时间内会不会遇到停盘,如果有停盘，再加上停盘时间
            try {
                ITimeDomain timeDomain = dataService.getOfflineTimeDomain();
                long next_time = context.getTime() + next_add_pause_time * 60000;
                if (next_time > timeDomain.getStart()) {
                    offline_stop = (int) (timeDomain.getEnd() - timeDomain.getStart()) / 60000;
                }
            } catch (JFException e) {
                if (log_error)
                    warn(" get offline time error,error=%s", e);
            }
        }
        OpenType openType = OpenType.P;
        //下面两个数值，
        ApolloOrderComment orderComment = new ApolloOrderComment(series_id, series_index, openType, first_equit, first_time.getTime(), this_profitloss, ylzc, tp, first_open_price, pre_open_price, current_open_price, next_add_pause_time + offline_stop);
        String comment = orderComment.toString();
        try {
            engine.mergeOrders(label, comment, mergeOrderList);
        } catch (JFException e) {
            warn("config[%d] %s.%s merge order[%s]-[%s]:%s", configIndex, instrument.name(), tradeDirection.name(), label, comment, e.getMessage());
        }
    }

    //根据压力支撑位来计算时间是否要翻倍
    private int getPauseTimeByYlzc(InstrumentEntity ie, double pre_open_price, double current_open_price, TradeDirection tradeDirection) {
        List<YlzcDTO> ylzcList = new ArrayList<>();//先要找到符合要求的压力支撑位
        if (!ylzcMap.containsKey(ie.instrument)) {
            return 1;
        }
        if (tradeDirection.isLong()) {
            List<YlzcDTO> zcwList = ylzcMap.get(ie.instrument).zcw;
            if (zcwList == null || zcwList.isEmpty()) {
                return 1;
            }
            double min_zcw_price = pre_open_price;
            for (YlzcDTO zcw : zcwList) {
                if (zcw.value > min_zcw_price) {
                    continue;
                }
                if (zcw.value < current_open_price) {
                    break;
                }
                min_zcw_price = zcw.value;
                if (pre_open_price >= zcw.value && current_open_price <= zcw.value) {
                    ylzcList.add(zcw);
                }
            }
        } else {
            List<YlzcDTO> ylwList = ylzcMap.get(ie.instrument).ylw;
            if (ylwList == null || ylwList.isEmpty()) {
                return 1;
            }
            double max_ylw_price = pre_open_price;
            for (YlzcDTO ylw : ylwList) {
                if (ylw.value < max_ylw_price) {
                    continue;
                }
                if (ylw.value > current_open_price) {
                    break;
                }
                max_ylw_price = ylw.value;
                if (pre_open_price <= ylw.value && current_open_price >= ylw.value) {
                    ylzcList.add(ylw);
                }
            }
        }

        int result = 1;
        for (YlzcDTO ylzc : ylzcList) {
            if (ylzc.level - 1 < pause_times.length) {
                result += pause_times[ylzc.level - 1];
                if (log_ylzc)
                    warn("open %s, between[%s , %s],find %s=%s,pause_time=%d", tradeDirection.name(), ie.formatPrice(pre_open_price), ie.formatPrice(current_open_price), (tradeDirection.isLong() ? "zcw" : ""), ylzc.toString(), +pause_times[ylzc.level - 1]);
            }
        }
        return result;
    }

    //找压力支撑位
    private YlzcDTO findYlzc(long closeTime, Instrument instrument, TradeDirection tradeDirection) {
        YlzcMap ym = ylzcMap.get(instrument);
        if (ym == null) {
            return null;
        }
        List<YlzcDTO> ylzcList = tradeDirection.isLong() ? ym.zcw : ym.ylw;
        if (ylzcList == null) {
            return null;
        }
        for (YlzcDTO ylzc : ylzcList) {
            if (ylzc.thedate.getTime() > closeTime) {
                return ylzc;
            }
        }
        return null;
    }


    private static YlzcDTO getFirstYlzcByLevel(double open_price, int level, TradeDirection tradeDirection, List<YlzcDTO> ylzcList) {
        double limit_price = open_price;
        for (YlzcDTO ylzc : ylzcList) {
            if (tradeDirection.isLong()) {
                if (ylzc.value > limit_price) {
                    continue;
                }
                limit_price = ylzc.value;
                if (ylzc.level == level) {
                    return ylzc;
                }
            } else {
                if (ylzc.value < limit_price) {
                    continue;
                }
                limit_price = ylzc.value;
                if (ylzc.level == level) {
                    return ylzc;
                }
            }
        }
        return null;
    }


    //删除同方向的挂单
    private void closeLimit(OrderEx closeOrderEx) {
        try {
            List<IOrder> orderList = engine.getOrders(closeOrderEx.instrument);
            for (IOrder order : orderList) {
                OrderEx orderEx = new OrderEx(order, new ApolloOrderComment());
                if (!orderEx.valid(closeOrderEx.name, closeOrderEx.configIndex)) {
                    continue;
                }
                TradeDirection tradeDirection = closeOrderEx.tradeDirection;
                IEngine.OrderCommand o2 = order.getOrderCommand();
                if ((tradeDirection.isLong() && o2 == IEngine.OrderCommand.BUYLIMIT)
                        || (tradeDirection == TradeDirection.SHORT && o2 == IEngine.OrderCommand.SELLLIMIT)) {
                    if (order.getState() == IOrder.State.OPENED) {
                        order.close();
                        order.waitForUpdate(500, IOrder.State.CANCELED);
                    }
                }
            }
        } catch (JFException e) {
            if (log_error)
                out("config[%d] close %s Limit error:", closeOrderEx.configIndex, closeOrderEx.tradeDirection.name());
        }
    }

    @Override
    public void onAccountEx(IAccount account) {
        double percent = account.getEquity() * 100 / account.getBalance();
        if (percent < 100 - minEquit) {
            warn("Equity:%.2f/Balance:%.2f=%.2f < %d %,all order close! EA Quit", account.getEquity(), account.getBalance(), percent, (100 - minEquit));
            context.stop();
        }
    }

    //加仓数列管理
    final class ATRSeriesManager {
        final List<Series> seriesList = new ArrayList();
        boolean valid;

        ATRSeriesManager(int[] seriesIds) {
            for (int seriesId : seriesIds) {
                addSeries(seriesId);
            }
            valid = seriesList.size() == seriesIds.length;
        }

        void addSeries(int seriesId) {
            Series series = new Series(seriesId);
            if (series.valid) seriesList.add(series);
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            for (Series series : seriesList) {
                sb.append(series.seriesId)
                        .append(".series.num=")
                        .append(series.list.size())
                        .append(",totalPip=")
                        .append(series.totalPip).append("\n");
            }
            return sb.toString();
        }

        //根据波动率找到对应的加仓数列
        Series getSeriesByValue(int atr, int maxATRSeries) {
            int max_diff = maxATRSeries;
            Series result = null;
            for (Series series : seriesList) {
                int diff = Math.abs(series.totalPip - atr);
                if (diff < max_diff) {
                    max_diff = diff;
                    result = series;
                }
            }
            return result;
        }

        //根据波动率找到对应的加仓数列
        Series getSeriesById(int id) {
            for (Series series : seriesList) {
                if (series.seriesId == id) {
                    return series;
                }
            }
            return null;
        }
    }

    //加仓数列
    final class Series {
        final int seriesId;//加仓数列ID
        final List<ForexConfigSeriesDTO> list = new ArrayList<>();//加仓数列
        int totalPip;//总抗单点位
        boolean valid;

        Series(int seriesId) {
            this.seriesId = seriesId;
            ResultDTO<List<ForexConfigSeriesDTO>> resultDTO = forexServiceProxy.findConfigSeries(seriesId);
            if (!resultDTO.okData() || resultDTO.data.isEmpty()) {
                valid = false;
                return;
            }
            list.addAll(resultDTO.data.stream().filter(x -> x.addpip != null
                    && x.backpip != null
                    && x.backpip != 0
                    && x.tp != null
                    && x.tp != 0
                    && x.time != null
                    && x.time != 0).collect(Collectors.toList()));
            totalPip = list.stream().mapToInt(x -> x.addpip).sum();
            valid = true;
        }

        @Override
        public String toString() {
            return "ATRSeries,series.length" + list.size();
        }

        ForexConfigSeriesDTO get(int index) {
            return index < 0 || index > list.size() - 1 ? null : list.get(index);
        }

        int size() {
            return list.size();
        }
    }


    private YlzcMap getYlzc(Instrument instrument, long time, int nums) {
        Date endDate = CalendarUtils.getFirstTime(new Date(time), cn.skyquant.quant4j.sdk.util.time.Period.daily);
        Date beginDate = CalendarUtils.addDate(endDate, -nums);
        ResultDTO<YlzcMap> resultDTO = ylzcServiceProxy.query(instrument.name(), 86400, beginDate, endDate);
        return resultDTO.data;
    }

    //阿波罗逆市单配置
    final static class ApolloConfig extends BaseConfig {
        final Instrument instrument;//货币
        final double atr;//atr倍率
        final double boll;//boll倍率
        final TradeDirection tradeDirection;//boll倍率

        ApolloConfig(String name, int index, double m, double lossLimit, Instrument instrument, double atr, double boll, String tradeDirection) {
            super(name, index, m, lossLimit);
            this.instrument = instrument;
            this.atr = atr;
            this.boll = boll;
            this.tradeDirection = TradeDirection.parse(tradeDirection);
        }

        @Override
        public String toString() {
            return "Apollo_" + index
                    + "_" + instrument.name()
                    + "_o_" + tradeDirection.name()
                    + "_m_" + m
                    + "_atr_" + atr
                    + "_boll_" + boll;
        }

        @Override
        public boolean isValid() {
            return instrument != null && m > 0 && atr > 0 && boll > 0 && tradeDirection != null;
        }
    }

    /**
     * 数列id_第几仓_加仓类型_首仓净值_首仓时间_首仓价格_
     */
    final static class ApolloOrderComment extends BaseOrderComment {
        double ylzc;                //本次加仓的用于止损的压力支撑价格6
        double tp;       //止盈价格 7
        double first_open_price;      //首仓price   8
        double pre_open_price;        //上一仓price 9
        double open_price;    //当前仓price 10
        int next_add_pause_time;//下一次加仓暂停时间(0代表需要止损) 11

        ApolloOrderComment() {
            super();
        }

        public void parse(String comment) {
            super.parse(comment);
            if (s.length >= getParamNum()) {
                try {
                    ylzc = Double.parseDouble(s[6]);
                    tp = Double.parseDouble(s[7]);
                    first_open_price = Double.parseDouble(s[8]);
                    pre_open_price = Double.parseDouble(s[9]);
                    open_price = Double.parseDouble(s[10]);
                    next_add_pause_time = Integer.parseInt(s[11]);
                    valid = true;
                } catch (Exception e) {
                    valid = false;
                }
            }
        }

        ApolloOrderComment(int series_id, int series_index, OpenType type, int first_equit, long first_time, int this_profitloss, double ylzc, double tp, double first_open_price, double pre_open_price, double open_price, int next_add_pause_time) {
            super(series_id, series_index, type, first_equit, first_time, this_profitloss);
            this.ylzc = ylzc;
            this.tp = tp;
            this.first_open_price = first_open_price;
            this.pre_open_price = pre_open_price;
            this.open_price = open_price;
            this.next_add_pause_time = next_add_pause_time;
        }

        public String toString() {
            return super.toString() + EAConstants.split
                    + ylzc + EAConstants.split
                    + tp + EAConstants.split
                    + first_open_price + EAConstants.split
                    + pre_open_price + EAConstants.split
                    + open_price + EAConstants.split
                    + next_add_pause_time;
        }

        @Override
        public int getParamNum() {
            return super.getParamNum() + 6;
        }
    }

    private static List<OrderEx> getSameOrders(List<IOrder> orderList, Instrument instrument, String name, int configIndex,
                                               TradeDirection tradeDirection, LabelType labelType, Class<? extends BaseOrderComment> c) {
        List<OrderEx> result = new ArrayList<>();
        //先遍历所有的订单，找到与当前订单类似的
        for (IOrder order : orderList) {
            if (order == null || order.getInstrument() != instrument || order.getState() != IOrder.State.FILLED) {
                continue;
            }
            BaseOrderComment orderComment = new ApolloOrderComment();
            OrderEx orderEx = new OrderEx(order, orderComment);
            if (!orderEx.valid(name, configIndex) || tradeDirection != orderEx.tradeDirection) {
                continue;
            }
            if (labelType != null && labelType != orderEx.labelType) {
                continue;
            }
            result.add(orderEx);
        }
        result.sort((a, b) -> a.seriesIndex - b.seriesIndex);//按订单序列号排序
        result.sort(Comparator.comparing(OrderEx::getSeriesIndex));
        result.sort(Comparator.comparingInt(x -> x.seriesIndex));
        return result;
    }

    private static OrderExManager getOrderExManager(BaseConfig baseConfig, List<IOrder> orderList, Class<? extends BaseOrderComment> c) {

        final OrderExManager o = new OrderExManager();
        for (IOrder order : orderList) {
            BaseOrderComment orderComment = new ApolloOrderComment();
            OrderEx orderEx = new OrderEx(order, orderComment);
            if (!orderEx.valid(baseConfig.name, baseConfig.index) || !orderEx.orderComment.valid) {
                continue;
            }

            if (order.getOrderCommand() == IEngine.OrderCommand.BUY && order.getState() == IOrder.State.FILLED) {
                o.buyList.add(orderEx);
                o.buy_pl_usd += order.getProfitLossInUSD();
                o.buy_pl_pip += order.getProfitLossInPips();
                o.buy_c += order.getCommissionInUSD();
                if (o.buy_first == null || orderEx.seriesIndex < o.buy_first.seriesIndex) {
                    o.buy_first = orderEx;
                }
                if (o.buy_last == null || orderEx.seriesIndex > o.buy_last.seriesIndex) {
                    o.buy_last = orderEx;
                }
            } else if (order.getOrderCommand() == IEngine.OrderCommand.SELL && order.getState() == IOrder.State.FILLED) {
                o.sellList.add(orderEx);
                o.sell_pl_usd += order.getProfitLossInUSD();
                o.sell_pl_pip += order.getProfitLossInPips();
                o.sell_c += order.getCommissionInUSD();
                if (o.sell_first == null || orderEx.seriesIndex < o.sell_first.seriesIndex) {
                    o.sell_first = orderEx;
                }
                if (o.sell_last == null || orderEx.seriesIndex > o.sell_last.seriesIndex) {
                    o.sell_last = orderEx;
                }
            } else if (order.getOrderCommand() == IEngine.OrderCommand.SELLLIMIT && order.getState() == IOrder.State.OPENED) {
                o.sellLimitList.add(orderEx);
            } else if (order.getOrderCommand() == IEngine.OrderCommand.BUYLIMIT && order.getState() == IOrder.State.OPENED) {
                o.buyLimitList.add(orderEx);
            } else if (order.getOrderCommand() == IEngine.OrderCommand.BUYSTOP && order.getState() == IOrder.State.OPENED) {
                o.buyStopList.add(orderEx);
            } else if (order.getOrderCommand() == IEngine.OrderCommand.SELLSTOP && order.getState() == IOrder.State.OPENED) {
                o.buyStopList.add(orderEx);
            }
        }
        return o;
    }
}
